# Copyright 2018 Maco
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from tornado import gen
from tornado.options import options
from blockchain.common.util import printf, request
from blockchain.common.constants import CODE_OK


def divide(nodes, channel=3):
    '''
    每次传播指定n个节点
    :param nodes: <list> 节点列表
    :param channel: <int> 每次广播的通道数
    :return: <list> [['node1'],['node2']]
    '''
    if len(nodes) < channel:
        # divide all nodes
        return [[node] for node in nodes]
    datas = list()
    index = 0
    max_len = len(nodes)
    remainder = (max_len - channel) % channel
    result = max_len // channel
    if result != 0:
        for idx in range(0, channel):
            datas.append(nodes[index:index + result])
            index += result
    if remainder != 0:
        last_nodes = nodes[max_len - remainder:]
        for idx in range(0, remainder):
            datas[idx] = datas[idx] + [last_nodes[idx]]
    return datas


@gen.coroutine
def broadcast_all(nodes, url, method='POST', data=None):
    '''
    向所有节点进行广播(或通过P2P方式附近节点广播，减少广播耗时)
    :param nodes: <list> 所有节点
    :param url: <str> 请求的url,端口后半段 
        e.g: http://127.0.0.1:8888/api/test url='/api/test'
    :param method: <str> 请求的方法，默认POST
    :param data: <dict> 请求的数据(body)，默认发送json
    :return: True 已全部广播；False 广播失败，节点不存在
    '''
    if not nodes:
        raise gen.Return(False)
    else:
        flag = True
        cache_nodes = list()
        data = data if data else dict()
        while flag:
            if cache_nodes:
                result = [node[1:] for node in cache_nodes if len(node) > 1]
                cache_nodes = list()
            else:
                result = divide(nodes)
            for lis in result:
                if not lis:
                    continue
                node = lis[0]
                data['_nodes'] = lis[1:]
                res = json.dumps(data)
                msg = 'Server:%s ---> %s.' % (options.port, node)
                printf(msg)
                resp = yield request(f'http://{node}{url}', method, body=res if res else None)
                if resp is None or resp.code != CODE_OK:
                    printf(f'Bad node: {node}.')
                    cache_nodes.append(lis)
            if not cache_nodes:
                flag = False
        raise gen.Return(True)


@gen.coroutine
def bc_register_node(nodes, data=None):
    '''
    当有新的节点注册时，向所以节点广播新增新的节点
    :param nodes: <list> 需要同步的节点
    '''
    url = '/api/node/rc/sync'
    result = broadcast_all(nodes, url, data={'nodes': data})
    raise gen.Return(result)


@gen.coroutine
def bc_new_block(nodes, block):
    '''
    当前节点的工作量证明(挖矿)广播到其它节点，其它节点验证是否正确
    :param nodes: <list> 需要同步的节点
    :param block: <dict> 块
    '''
    url = '/api/chain/proof/vaild'
    result = broadcast_all(nodes, url, data={'block': block})
    raise gen.Return(result)


@gen.coroutine
def bc_new_transaction(nodes, transaction):
    '''
    如果交易在当前节点接收，向所以节点广播同步数据，需注意的是交易排序问题。
    :param nodes: <list> 需要同步的节点
    :param transaction: <dict> e.g:{"sender":"a","recipient":"b,"amount:1}
    '''
    url = '/api/transaction/rc/sync'
    result = broadcast_all(nodes, url, data={'ntx': transaction})
    raise gen.Return(result)
